#pragma once
#include <iostream>
#include <sys/types.h> // ftok(), shmat()
#include <sys/ipc.h>   // ftok(), shmget(), shmctl()
#include <sys/shm.h>   // shmget(), shmat(), shmctl()
#include <cstring>     // strerror()
#include <unistd.h>    // sleep()
#include <vector>
#include <algorithm>

using namespace std;

const char *path = "./";
key_t proj_id = 0x66;

std::string keyToHexByString(key_t key)
{
    char buffer[1024];
    snprintf(buffer, sizeof(buffer) - 1, "0x%x", key);
    return buffer;
}

class ShmIpc
{
public:
    ShmIpc(const char *pathname, int proj_id, int size = 4096)
        : _pathname_s(pathname), _proj_id(proj_id), _size(size), _shm_addr(nullptr)
    {
        _key = GetShmKeyOrDie();
        _shmid_ifset = make_pair(false, 0);
    }
    virtual ~ShmIpc()
    {
        _shm_addr = nullptr;
    }

public:
    virtual void Run() = 0;

protected:
    int SetShmOrDie(int shmflg)
    {
        int shmid = shmget(_key, _size, shmflg);
        if (shmid == -1)
        {
            std::cout << "Error: ShmIpc::SetShmOrDie:: shmget(), errno: " << errno << " ,error message: " << strerror(errno) << endl;
            exit(1);
        }

        _shmid_ifset.first = true;
        _shmid_ifset.second = shmid;
        return shmid;
    }
    int getShm_nattch() // to get nattch num of shmid
    {
        struct shmid_ds ds_buf;
        if (shmctl(_shmid_ifset.second, IPC_STAT, &ds_buf) == -1)
        {
            perror("getShm_nattch:: shmctl error !");
            return -1;
        }
        return ds_buf.shm_nattch;
    }
    void *ShmAttach()
    {
        if (!_shmid_ifset.first)
        {
            perror("Error: ShmAttach:: shmid had not been set !");
            exit(1);
        }
        void *addr = shmat(_shmid_ifset.second, nullptr, 0);
        if ((long long int)addr == -1)
        {
            std::cout << "Error: ShmIpc::ShmAttach:: shmat(), errno: " << errno << " ,error message: " << strerror(errno) << endl;
            return nullptr;
        }
        _shm_addr = addr;
        return _shm_addr;
    }
    int ShmDetach()
    {
        if (_shm_addr == nullptr)
        {
            perror("Error: ShmDetach:: share memory address is nullptr !");
            exit(1);
        }

        int dt = shmdt(_shm_addr);
        printf("ShmDetach ......\n");       // print the prompt message
        sleep(2);

        if (dt == -1)
        {
            std::cout << "Error: ShmIpc::ShmDetach:: shmdt(), errno: " << errno << " ,error message: " << strerror(errno) << endl;
        }
        return dt;
    }
    void DeleteShm()        // general called by Server
    {
        if (_shmid_ifset.first == false)
        {
            perror("Error: DeleteShm:: shmid had not been set !");
            return;
        }

        int n = shmctl(_shmid_ifset.second, IPC_RMID, nullptr); // remove share memory id
        printf("DeleteShm ......\n");       // print the prompt message
        sleep(2);

        if (n < 0)
        {
            std::cout << "Error: ShmIpc::DeleteShm:: shmctl()" << std::endl;
        }
        else
        {
            std::cout << "Delete share memory success..." << std::endl;
        }
    }

private:
    key_t GetShmKeyOrDie()
    {
        key_t key = ftok(_pathname_s.c_str(), _proj_id);
        if (key == -1)
        {
            std::cout << "Error: ShmIpc::GetShmKeyOrDie:: ftok(), errno: " << errno << " ,error message: " << strerror(errno) << endl;
            exit(1);
        }
        return key;
    }

protected:
    string _pathname_s; // set path for get key and FIFO pipe
    int _proj_id;       // a random number been set for get key
    int _size;          // the shm size convention
    key_t _key;
    pair<bool, int> _shmid_ifset; // when first is false mean that shmid had not been set
    void *_shm_addr;              // it is nullptr when never been set
};

class Server : public ShmIpc
{
public:
    Server(const char *pathname, int proj_id, int size = 4096) : ShmIpc(pathname, proj_id, size)
    {
        // 1.set key work is done by father constructor

        // 2.create share memory id
        SetShmOrDie(IPC_CREAT | IPC_EXCL | 0666);   // "IPC_CREAT | IPC_EXCL" -> if exist: segment-error, else:create new
        // 3.attach process to the share memory
        ShmAttach();
    }

    // 4.receive message
    virtual void Run()
    {
        while(getShm_nattch() == 1)
        {
            printf("Waiting for Client...\n");
            sleep(1);
        }
        while (getShm_nattch() == 2) // run only on client has connected the share memory
        {
            printf("%s\n", (char*)_shm_addr);
            sleep(1);
        }
    }

    ~Server()
    {
        // 5.drop the connection between process and shm
        ShmDetach();

        // 6.delete share memory
        DeleteShm();
    }
};

class Client : public ShmIpc
{
public:
    Client(const char *pathname, int proj_id, int size = 4096) : ShmIpc(pathname, proj_id, size)
    {
        // 1.set key work is done by father constructor

        // 2.get share memory id
        SetShmOrDie(IPC_CREAT);
        // 3.attach process to the share memory
        ShmAttach();
    }

    // 4.send message
    virtual void Run()
    {
        char *str = (char *)_shm_addr;
        memset(str, 0, _size);

        for (char c = 'A'; c <= 'G'; c++)
        {
            str[c - 'A'] = c;
            sleep(1);
        }
    }

    ~Client()
    {
        // 5.drop the connection between process and shm
        ShmDetach();
        // 6.delete share memory had done by Server
    }
};
